//
// Copyright (c) 2009 All Right Reserved
//
// vl
//
// 2009-01-01
// Contains ...
using System;
using System.Diagnostics.Contracts;
using System.Text;
using System.Xml.Linq;
using System.Xml.Serialization;
using LargoCommon.Abstract;
using LargoCommon.Interfaces;
using LargoCommon.Midi;
namespace LargoCommon.Music
{
/// Musical tone.
/// Tone is defined in given bit Range with a given Loudness.
[Serializable]
[XmlRoot]
public class MusicalStrike : GeneralOwner, IMusicalTone
{
#region Fields and variables
/// Mark for musical pause.
public const string CPause = " ";
/// Mark for rhythmical bang.
public const string CBeat = "!";
/// Mark for duration.
public const string CRepeat = "-";
#endregion
#region Constructors
/// Initializes a new instance of the MusicalStrike class. Serializable.
public MusicalStrike() {
this.OrdinalIndex = -1;
}
/// Initializes a new instance of the MusicalStrike class.
/// Type of tone.
/// Rhythmical range of bits.
/// Musical loudness.
/// Number of musical bar.
public MusicalStrike(
MusicalToneType toneType,
BitRange givenBitRange,
MusicalLoudness loudness,
int barNumber) {
this.ToneType = toneType;
this.BitRange = givenBitRange;
this.Loudness = loudness;
this.BarNumber = barNumber;
}
/// Initializes a new instance of the MusicalStrike class.
/// Type of tone.
/// Rhythmical order.
/// Musical duration.
/// Musical loudness.
/// Number of musical bar.
public MusicalStrike(
MusicalToneType toneType,
byte givenRhythmicOrder,
byte givenDuration,
MusicalLoudness loudness,
int barNumber) {
this.ToneType = toneType;
this.RhythmicOrder = givenRhythmicOrder;
this.BitFrom = 0;
this.Duration = givenDuration;
this.Loudness = loudness;
this.BarNumber = barNumber;
}
///
/// Initializes a new instance of the class.
///
/// The xml element.
/// The rhythmic order.
public MusicalStrike(XElement xelement, byte rorder) {
Contract.Requires(xelement != null);
//// if (xelement == null) { return; }
//// this.bitRange = new BitRange();
//// this.bitRange.SetXElement(element);
this.BarNumber = XmlSupport.ReadIntegerAttribute(xelement.Attribute("Bar"));
this.BitFrom = XmlSupport.ReadByteAttribute(xelement.Attribute("Start"));
this.Duration = XmlSupport.ReadByteAttribute(xelement.Attribute("Length"));
this.BitRange = new BitRange(rorder, this.BitFrom, (byte)this.Duration);
//// string s = LibSupport.ReadStringAttribute(xelement.Attribute("ToneType"));
//// this.ToneType = string.IsNullOrEmpty(s) ? MusicalToneType.Empty : (MusicalToneType)Enum.Parse(typeof(MusicalToneType), s);
this.ToneType = MusicalToneType.Rhythmic;
this.Loudness = DataEnums.ReadAttributeMusicalLoudness(xelement.Attribute("Loudness"));
var attrInstr = xelement.Attribute("Instrument");
if (attrInstr != null) {
this.InstrumentNumber = XmlSupport.ReadByteAttribute(attrInstr);
}
else {
this.InstrumentNumber = (byte)MidiMelodicInstrument.None;
}
}
#endregion
#region Properties - Xml
/// Gets Xml representation.
/// Property description.
public virtual XElement GetXElement {
get {
var xe = new XElement(
"Tick",
new XAttribute("Bar", this.BarNumber),
new XAttribute("Start", this.BitFrom),
new XAttribute("Length", this.Duration));
if (this.Loudness != MusicalLoudness.MeanLoudness) {
xe.Add(new XAttribute("Loudness", this.Loudness.ToString()));
}
return xe;
}
}
#endregion
#region Tone Properties
/// Gets or sets type of musical tone.
/// Property description.
public MusicalToneType ToneType { get; set; }
/// Gets or sets ordinal index of tone in musical track.
/// Property description.
public int OrdinalIndex { get; set; }
/// Gets or sets loudness.
/// Property description.
public MusicalLoudness Loudness { get; set; }
/// Gets or sets a value indicating whether property of musical tone.
/// Property description.
public bool IsReady { get; set; }
/// Gets or sets bar number.
/// Property description.
public int BarNumber { get; set; }
/// Gets or sets staff number (MusicXml).
/// Property description.
public byte Staff { get; set; }
/// Gets or sets voice number (MusicXml).
/// Property description.
public byte Voice { get; set; }
///
/// Gets or sets a value indicating whether this instance is from previous bar.
///
///
/// True if this instance is from previous bar; otherwise, false.
///
public bool IsFromPreviousBar { get; set; }
///
/// Gets or sets a value indicating whether [continue to next bar].
///
///
/// True if [continue to next bar]; otherwise, false.
///
public bool IsGoingToNextBar { get; set; }
/// Gets property of musical tone.
/// Property description.
public int BitPosition => ((this.BarNumber - 1) * this.RhythmicOrder) + this.BitFrom;
/// Gets or sets staff number (MusicXml).
/// Property description.
public byte RhythmicOrder { get; set; }
/// Gets or sets BitFrom.
/// Property description.
public byte BitFrom { get; set; }
/// Gets or sets staff number (MusicXml).
/// Property description.
public int Duration { get; set; }
///
/// Gets or sets the bit range.
///
///
/// The bit range.
///
public BitRange BitRange {
get {
Contract.Ensures(Contract.Result() != null);
var bitRange = new BitRange(this.RhythmicOrder, this.BitFrom, (byte)this.Duration);
return bitRange;
}
set {
var bitRange = value;
if (bitRange == null) {
return;
}
this.RhythmicOrder = bitRange.Order;
this.BitFrom = bitRange.BitFrom;
this.Duration = bitRange.Length;
}
}
/// Gets BitTo.
/// Property description.
public byte BitTo => this.BitRange.BitTo;
/// Gets a value indicating whether IsPause.
/// General musical property.
/// Returns value.
public bool IsPause => (this.ToneType == MusicalToneType.Empty) || (this.Loudness == 0);
///
/// Gets a value indicating whether this instance is empty.
///
///
/// true if this instance is empty; otherwise, false.
///
public virtual bool IsEmpty => this.IsPause;
/// Gets or sets index to harmonic structure.
/// Musical instrument.
///
/// Property description.
public byte InstrumentNumber { get; set; }
#endregion
#region Note Properties
///
/// Gets or sets the length of the note.
///
///
/// The length of the note.
///
public NoteLength NoteLength { get; set; }
/// Gets The MIDI note to modify (0x0 to 0x7F).
/// General musical property.
public virtual byte NoteNumber => 0;
/// Gets the MIDI note to modify (0x0 to 0x7F).
/// General musical property.
public virtual string Note => string.Empty;
#endregion
#region Other Properties
///
/// Gets the melodic identifier.
///
///
/// The melodic identifier.
///
public virtual string MelodicIdentifier => $"Tick#{this.BitFrom}/{this.Duration}";
///
/// Gets the rhythmic identifier.
///
///
/// The rhythmic identifier.
///
public virtual string RhythmicIdentifier => $"{this.BitFrom}/{this.Duration}";
#endregion
#region Public static methods
///
/// Gets the new musical tone.
///
/// The given header.
/// The quotient.
/// The midi tone.
/// The bar number.
/// The real bar number.
/// if set to true [melodic track].
///
/// Returns value.
///
public static IMusicalTone GetNewMusicalTone(
MusicalHeader givenHeader,
int quotient,
IMidiTone midiTone,
int barNumber,
int realBarNumber,
bool melodicTrack) {
//// this.Strip.Context.Header //// this.Status.IsMelodic
IMusicalTone musTone;
var bitFrom = (int)Math.Round((double)midiTone.StartTime / quotient, 0);
var bitTo = (int)Math.Round((double)(midiTone.StartTime + midiTone.Duration) / quotient, 0) - 1;
var rhythmicOrder = givenHeader.System.RhythmicOrder;
var bitLimit = rhythmicOrder * realBarNumber; //// Zero bit of next bar
var bitZero = bitLimit - rhythmicOrder;
if (bitTo < bitZero) {
return null;
}
var isFromPreviousBar = barNumber > midiTone.BarNumberFrom;
var continueToNextBar = barNumber < midiTone.BarNumberTo && bitTo >= bitLimit;
var barBitFrom = !isFromPreviousBar ? bitFrom % rhythmicOrder : 0;
var barBitTo = !continueToNextBar ? bitTo % rhythmicOrder : rhythmicOrder - 1;
var duration = barBitTo - barBitFrom + 1;
if (duration <= 0) {
return null;
}
var bitRange = new BitRange(rhythmicOrder, (byte)barBitFrom, (byte)duration);
if (midiTone.Loudness == MusicalLoudness.None) {
return null;
/* 2019/02 Reduction of redundant pauses, here was == MusicalLoudness.None ==>
mtone = MusicalPause.CreatePause(rhythmicOrder, (byte)barBitFrom, (byte)duration, barNumber);
return mtone; */
}
if (melodicTrack) {
var hs = givenHeader.System.HarmonicSystem;
musTone = MusicalTone.CreateMelodicTone(hs, bitRange, midiTone.NoteNumber, barNumber, midiTone.Loudness);
}
else {
musTone = new MusicalStrike(MusicalToneType.Rhythmic, bitRange, midiTone.Loudness, barNumber);
}
musTone.ToneType = melodicTrack ? MusicalToneType.Melodic : MusicalToneType.Rhythmic;
/* 2019/02 if (!(mtone is MusicalStrike musTone)) { return null; } */
////if (midiTone.Loudness != 0) {
musTone.InstrumentNumber = melodicTrack ? midiTone.InstrumentNumber : midiTone.NoteNumber;
//// musTone.Channel = this.Status.IsMelodic ? midiTone.Channel : MidiChannel.DrumChannel;
////}
//// else { musTone.IsFromPreviousBar = false; musTone.IsGoingToNextBar = false; }
musTone.IsFromPreviousBar = isFromPreviousBar;
musTone.IsGoingToNextBar = continueToNextBar;
//// musTone.BarNumberTo = midiTone.BarNumberTo;
return musTone;
}
///
/// Corrects the incorrect binding.
///
/// The first tone.
/// The next tone.
public static void CorrectBadBinding(MusicalStrike firstTone, MusicalStrike nextTone) {
if (firstTone == null || nextTone == null) {
return;
}
if (firstTone.IsGoingToNextBar && !nextTone.IsFromPreviousBar) {
firstTone.IsGoingToNextBar = false;
}
if (nextTone is MusicalTone nextMelTone && firstTone is MusicalTone firstMelTone && firstTone.IsGoingToNextBar
&& firstMelTone.Pitch.SystemAltitude != nextMelTone.Pitch.SystemAltitude) {
firstTone.IsGoingToNextBar = false;
nextTone.IsFromPreviousBar = false;
}
if (!firstTone.IsGoingToNextBar || firstTone.BarNumber == nextTone.BarNumber - 1) {
return;
}
firstTone.IsGoingToNextBar = false;
nextTone.IsFromPreviousBar = false;
}
#endregion
#region Public methods
///
/// Accept content of given tone.
///
/// Musical Strike.
public void SetMusicalTone(MusicalStrike tone) {
if (tone == null) {
return;
}
//// this.SetBitRange(tone.BitRange());
this.BitRange = (BitRange)tone.BitRange.Clone();
this.Loudness = tone.Loudness;
this.ToneType = tone.ToneType;
this.BarNumber = tone.BarNumber;
if (tone.Properties != null) {
this.CopyProperties(tone.Properties);
}
}
///
/// Makes a deep copy of the GeneralOwner object.
///
///
/// Returns object.
///
public override object Clone() {
return this.CloneTone();
}
/// Makes a deep copy of the MusicalStrike object.
/// Returns object.
public virtual object CloneTone() {
var mt = new MusicalStrike(this.ToneType, this.BitRange, this.Loudness, this.BarNumber) //// (this.BarNumber)
{ OrdinalIndex = this.OrdinalIndex };
return mt;
}
#endregion
#region Public Midi Support
///
/// Write real tone to midi collection.
///
/// Midi event collection.
/// Bar division.
public void WriteTo(MidiEventCollection midiEvents, int barDivision) {
if (midiEvents == null) {
return;
}
//// BitRange br = this.BitRange(this.BarNumber);
if (this.RhythmicOrder <= 0) {
return;
}
const int startTime = 0;
var duration = MusicalProperties.MidiDuration(this.RhythmicOrder, this.Duration, barDivision);
var stopTime = startTime + duration;
byte note = 0;
//// byte channel = (byte)this.Channel;
var loudness = (byte)MusicalLoudness.None;
switch (this.ToneType) {
case MusicalToneType.Rhythmic: {
//// channel = (byte)MidiChannel.DrumChannel;
if (this.InstrumentNumber == 0) {
this.InstrumentNumber = (byte)MidiRhythmicInstrument.HighBongo;
}
loudness = (byte)this.Loudness; //// (byte)MusicalLoudness.MeanLoudness;
note = this.InstrumentNumber;
break;
}
case MusicalToneType.Melodic: {
if (!this.IsPause && (this.Loudness > 0)) { //// melTone != null && (melTone.Pitch != null) &&
if (this is MusicalTone melTone && melTone.IsTrueTone) {
note = melTone.Pitch.MidiKeyNumber; //// (byte)melTone.Pitch.MidiPitchBend(),
loudness = (byte)melTone.Loudness;
//// channel = (byte)this.Channel;
}
}
break;
}
}
midiEvents.PutNote(startTime, note, stopTime, loudness, this.IsFromPreviousBar, this.IsGoingToNextBar);
}
///
/// Write Tone Events.
///
/// Midi event collection.
/// Bar division.
/// Delta Time of bit.
/// Delta Time of bar.
/// Delta Time Shift.
/// Returns value.
public bool WriteTo(MidiEventCollection midiEvents, int barDivision, int bitDuration, int barDuration, int deltaTimeShift) { //// cyclomatic complexity 10:13
if (midiEvents == null) {
return false;
}
if (this.RhythmicOrder <= 0) {
return false;
}
var mtone = this;
var bitRangeTo = mtone.BitRange; //// (mtone.BarNumberTo)
if (mtone.RhythmicOrder <= 0 || bitRangeTo == null) {
return false;
}
var barStartTime = 1 + (barDuration * (mtone.BarNumber - 1)) - deltaTimeShift;
var startTime = barStartTime + (bitDuration * mtone.BitFrom); //// lastTotalTime = 0
var stopTime = barStartTime + (bitDuration * (mtone.BitTo + 1)) - 1;
if (stopTime <= startTime) {
return false;
}
//// midiEvent = this.GetNoteOff(deltaTime2 - deltaTimeShift - 1); //// Temporary - decreased by 1, see comment on SortByStartTime
switch (this.ToneType) {
case MusicalToneType.Rhythmic: {
this.WriteRhythmicToneTo(midiEvents, startTime, stopTime);
break;
}
case MusicalToneType.Melodic: {
this.WriteMelodicToneTo(midiEvents, startTime, stopTime);
break;
}
}
return true;
}
#endregion
#region String representation
/// String representation of the object.
/// Returns value.
public virtual string ToShortString() {
var s = new StringBuilder();
if (this.Loudness == 0) {
s.Append(MusicalStrike.CPause);
}
return s.ToString();
}
/// String representation of the object.
/// Returns value.
public override string ToString() {
var s = new StringBuilder(string.Empty);
//// if (this is MusicalTone) { return s.ToString(); }
//// s.AppendFormat(CultureInfo.CurrentCulture, "{0,3}", this.Loudness > 0 ? MusicalStrike.CBeat : MusicalStrike.CPause);
//// for (byte lev = 2; lev <= this.Duration; lev++) {
//// s.AppendFormat(CultureInfo.CurrentCulture, "{0,3}", MusicalStrike.CRepeat); }
s.AppendFormat(" <{0,3}>", this.Duration);
//// s.Append(",lev" + Loudness.ToString(System.Globalization.CultureInfo.CurrentCulture.NumberFormat) + ")");
//// s.Append(base.ToString());
return s.ToString();
}
///
/// Rhythmic to string.
///
/// Returns value.
public string RhythmicToString() {
var s = new StringBuilder(string.Empty);
//// if (this is MusicalTone) { return s.ToString(); }
s.AppendFormat(" <{0,3}>", this.Duration);
return s.ToString();
}
#endregion
#region Private methods
///
/// Writes the melodic tone to.
///
/// The midi events.
/// The start time.
/// The stop time.
private void WriteMelodicToneTo(MidiEventCollection midiEvents, int startTime, int stopTime) {
byte note = 0, loudness = (byte)MusicalLoudness.None;
if (!this.IsPause && (this.Loudness > 0)) { //// melTone != null && (melTone.Pitch != null) &&
if (this is MusicalTone melTone && melTone.IsTrueTone) {
note = melTone.Pitch.MidiKeyNumber; //// (byte)melTone.Pitch.MidiPitchBend(),
loudness = (byte)melTone.Loudness;
}
}
if (note > 0) {
midiEvents.PutNote(startTime, note, stopTime, loudness, this.IsFromPreviousBar, this.IsGoingToNextBar);
}
}
///
/// Writes the rhythmic tone to.
///
/// The midi events.
/// The start time.
/// The stop time.
private void WriteRhythmicToneTo(MidiEventCollection midiEvents, int startTime, int stopTime) {
Contract.Requires(midiEvents != null);
if (this.InstrumentNumber == 0) {
this.InstrumentNumber = (byte)MidiRhythmicInstrument.HighBongo;
}
var loudness = (byte)this.Loudness;
var note = this.InstrumentNumber;
//// if (note > 0) {
midiEvents.PutNote(startTime, note, stopTime, loudness, this.IsFromPreviousBar, this.IsGoingToNextBar);
//// }
}
#endregion
}
}